package com.huangfu.learning.javabase.java.generic;

/**
 * Created with IntelliJ IDEA.
 * Author: huangfu.daohui
 * Date: 2018/7/10
 * Time: 14:39
 * Version: 1.0
 * Description:泛型方法
 * 泛型类，是在实例化类的时候指明泛型的具体类型；泛型方法，是在调用方法的时候指明泛型的具体类型 。
 **/
public class GenericMethod {

    /**
     * @param tClass 传入的泛型实参
     * @param <T>
     * @return 返回值为T类型
     * 说明
     * 1、public与返回值T中间的<T>非常重要，可以理解为声明此方法没泛型方法
     * 2、只有声明了<T>的方法，才是泛型方法，泛型类中使用了泛型的成员方法并不是泛型方法
     * 3、<T>表名该方法将使用泛型类型T，此时才可以使用泛型类型T
     * 4、与泛型类的定义一样，此处T可以随便写为任意标识，常见的如T、E、K、V等形式的参数常用于表示泛型
     */
    public <T> T genericMethod(Class<T> tClass) throws IllegalAccessException, InstantiationException {
        T instence = tClass.newInstance();
        return instence;
    }


    static class Generic<T> {

        private T key;

        public Generic(T key) {
            this.key = key;
        }

        //我想说的其实是这个，虽然在方法中使用了泛型，但是这并不是一个泛型方法。
        //这只是类中一个普通的成员方法，只不过他的返回值是在声明泛型类已经声明过的泛型。
        //所以在这个方法中才可以继续使用 T 这个泛型。
        public T getKey() {
            return key;
        }

        /**
         * 这才是一个真正的泛型方法。
         * 首先在public与返回值之间的<T>必不可少，这表明这是一个泛型方法，并且声明了一个泛型T
         * 这个T可以出现在这个泛型方法的任意位置.
         * 泛型的数量也可以为任意多个
         * 如：public <T,K> K showKeyName(Generic<T> container){
         * ...
         * }
         */
        public <T> T showKeyName(Generic<T> container) {
            System.out.println("container key :" + container.getKey());
            //当然这个例子举的不太合适，只是为了说明泛型方法的特性。
            T test = container.getKey();
            return test;
        }

        //这也不是一个泛型方法，这就是一个普通的方法，只是使用了Generic<Number>这个泛型类做形参而已。
        public String showKeyValue1(Generic<Number> obj) {
            return "泛型测试 key value is " + obj.getKey();
        }

        //这也不是一个泛型方法，这也是一个普通的方法，只不过使用了泛型通配符?
        //同时这也印证了泛型通配符章节所描述的，?是一种类型实参，可以看做为Number等所有类的父类
        public String showKeyValue2(Generic<?> obj) {
            return "泛型测试 key value is " + obj.getKey();
        }


        public <T> void printMsg(T... args) {
            for (T t : args) {
                System.out.println("泛型可变参数：" + t);
            }
        }

    }

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        GenericMethod generic = new GenericMethod();
        Object obj = generic.genericMethod(GenericMethod.class);

        System.out.println(obj);//com.huangfu.learning.javabase.java.generic.GenericMethod@1b6d3586
        System.out.println("-----------------------------------");

        Generic<String> generic1 = new Generic<String>("zhang");
        System.out.println(generic1.getKey()); //zhang

        //注意：☆☆☆☆下面几个入参的类型 要是Generic<T>
        Generic<String> generic2 = new Generic<String>("huang");
        System.out.println(generic2.showKeyName(generic2)); //container key :huang  ;;;;;huang

        Generic<Number> generic3 = new Generic<Number>(12);
        System.out.println(generic3.showKeyValue1(generic3)); //泛型测试 key value is 12

        Generic<String> generic4 = new Generic<String>("123");
        System.out.println(generic4.showKeyValue2(generic4)); //泛型测试 key value is 123

        System.out.println("----------泛型可变参数------------");
        generic4.printMsg("111", 222, "aaaa", "2323.4", 55.55);
    }

    // T 只能是Number或者其子类
    static class GenericE<T extends Number> {
        private T key;

        public GenericE(T key) {
            this.key = key;
        }

        public T getKey() {
            return key;
        }

        //在泛型方法中添加上下边界限制的时候，必须在权限声明与返回值之间的<T>上添加上下边界，即在泛型声明的时候添加
        //public <T> T showKeyName(Generic<T extends Number> container)，编译器会报错："Unexpected bound"
        //泛型的上下边界添加，必须与泛型的声明在一起
        public <T extends Number> T showKeyName(GenericE<T> container) {
            System.out.println("container key :" + container.getKey());
            T test = container.getKey();
            return test;
        }

        public static void main(String[] args) {
            // 这里GenericE的类型只能是Number或者其子类，其他类型会报错
            GenericE<Number> genericE = new GenericE<Number>(1);
            genericE.showKeyName(genericE);
        }
    }
}
